package com.yc.sole.framework.redis;

import cn.hutool.core.text.CharSequenceUtil;
import com.yc.sole.framework.exception.BaseException;
import com.yc.sole.framework.redis.enums.CacheTime;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @author yizuomin
 * @date 2023/3/20 11:13
 **/
@Slf4j
@Component
@RequiredArgsConstructor
public class RedissonService {

    private final RedissonClient redissonClient;
    private final ThreadPoolTaskExecutor taskExecutor;

    /**
     * 设置自增并过期
     *
     * @param key key
     * @return
     */
    public Long incrementLongAndExpire(String key, CacheTime cacheTime) {
        long l = redissonClient.getAtomicLong(key).incrementAndGet();
        redissonClient.getAtomicLong(key).expire(cacheTime.getTime());
        return l;
    }

    /**
     * string set
     *
     * @param cacheTime 缓存过期时间
     * @param key       key
     * @param value     value
     */
    public <T> void setString(String prefix, String key, T value, CacheTime cacheTime) {
        RBucket<T> bucket = redissonClient.getBucket(prefix + ":" + key, JsonJacksonCodec.INSTANCE);
        bucket.set(value);
        bucket.expire(cacheTime.getTime());
    }

    /**
     * 获取string格式的key
     *
     * @param prefix
     * @param key
     * @param <T>
     * @return
     */
    public <T> T getString(String prefix, String key) {
        RBucket<T> bucket = redissonClient.getBucket(prefix + ":" + key, JsonJacksonCodec.INSTANCE);
        return bucket.get();
    }

    /**
     * 删除
     *
     * @param prefix
     * @param key
     * @param <T>
     */
    public <T> void removeString(String prefix, String key) {
        RBucket<T> bucket = redissonClient.getBucket(prefix + ":" + key, JsonJacksonCodec.INSTANCE);
        bucket.delete();
        CompletableFuture.runAsync(() -> {
            try {
                //延时200毫秒再删除一次
                Thread.sleep(200);
                bucket.delete();
                log.info("延时删除，key:{}", prefix + ":" + key);
            } catch (Exception e) {
                throw new BaseException(e.getMessage());
            }
        }, taskExecutor);
    }

    /**
     * 模糊删除String
     *
     * @param prefix
     * @param pattern
     * @param <T>
     */
    public <T> void removeStringPattern(String prefix, String pattern) {
        Iterable<String> keysByPattern = redissonClient.getKeys().getKeysByPattern(prefix + ":" + "*" + pattern + "*");
        for (String key : keysByPattern) {
            RBucket<Object> bucket = redissonClient.getBucket(prefix + ":" + key, JsonJacksonCodec.INSTANCE);
            if (bucket.isExists()) {
                bucket.delete();
                CompletableFuture.runAsync(() -> {
                    try {
                        //延时2秒再删除一次
                        Thread.sleep(2);
                        bucket.delete();
                    } catch (InterruptedException e) {
                        throw new BaseException(e);
                    }
                }, taskExecutor);
            }
        }
    }

    /**
     * hash数据类型get
     *
     * @param mapKey map key
     * @param hKey   hash key
     * @param <T>    类型
     */
    public <T> T hGet(String mapKey, Object hKey) {
        Object o = redissonClient.getMap(mapKey, JsonJacksonCodec.INSTANCE).get(hKey);
        return Objects.isNull(o) ? null : (T) o;
    }

    /**
     * hash数据类型 put
     *
     * @param mapKey map key
     * @param hKey   hash key
     * @param hValue hash value
     * @param <T>    类型
     */
    public <T> void hPut(String mapKey, Object hKey, T hValue) {
        this.hPut(mapKey, hKey, hValue, CacheTime.ONE_DAY);
    }

    /**
     * hash数据类型 put
     *
     * @param mapKey map key
     * @param hKey   hash key
     * @param hValue hash value
     * @param <T>    类型
     */
    public <T> void hPut(String mapKey, Object hKey, T hValue, CacheTime cacheTime) {
        if (CharSequenceUtil.isNotBlank(mapKey) && Objects.nonNull(hKey)) {
            redissonClient.getMap(mapKey, JsonJacksonCodec.INSTANCE).put(hKey, hValue);
            redissonClient.getMap(mapKey, JsonJacksonCodec.INSTANCE).expire(cacheTime.getTime());
        }
    }

    /**
     * 缓存
     *
     * @param mapKey map key
     * @param hKey   hash key
     * @param fun    fun
     * @param <V>    t
     * @return r
     */
    public <K, V> V hCache(String mapKey, K hKey, Supplier<V> fun) {
        return this.hCache(mapKey, hKey, CacheTime.ONE_DAY, fun);
    }

    /**
     * 缓存
     *
     * @param mapKey map key
     * @param hKey   hash key
     * @param fun    fun
     * @param <V>    t
     * @return r
     */
    public <K, V> V hCache(String mapKey, K hKey, CacheTime cacheTime, Supplier<V> fun) {
        if (CharSequenceUtil.isBlank(mapKey) || Objects.isNull(hKey)) {
            throw new BaseException("缓存key不能为空");
        }
        V result = null;
        RMap<K, V> resultMap = null;
        try {
            resultMap = redissonClient.getMap(mapKey, JsonJacksonCodec.INSTANCE);
            resultMap.expire(cacheTime.getTime());
            result = resultMap.get(hKey);
            if (Objects.nonNull(result)) {
                return result;
            }
        } catch (Exception e) {
            log.error("redis error:缓存查询失败，请检查缓存服务：【{}】", e.getMessage());
        }
        if (Objects.isNull(result)) {
            result = fun.get();
        }
        if (Objects.nonNull(resultMap) && Objects.nonNull(result)) {
            resultMap.put(hKey, result);
        }
        return result;
    }

    /**
     * 删除
     *
     * @param mapKey       mapKey
     * @param hKey         hash key
     * @param deleteMethod run
     */
    public void hRemove(String mapKey, Object hKey, Runnable deleteMethod) {
        if (CharSequenceUtil.isBlank(mapKey) || Objects.isNull(hKey)) {
            throw new BaseException("缓存key不能为空");
        }
        RMap<Object, Object> map = redissonClient.getMap(mapKey, JsonJacksonCodec.INSTANCE);
        map.remove(hKey);
        deleteMethod.run();
        CompletableFuture.runAsync(() -> {
            try {
                //延时200毫秒再删除一次
                Thread.sleep(200);
                log.info("延时删除缓存，key:{}", mapKey + hKey);
                map.remove(hKey);
            } catch (InterruptedException e) {
                throw new BaseException(e);
            }
        }, taskExecutor);
    }

    /**
     * 全部删除
     *
     * @param mapKey mapKey
     */
    public void hRemoveAll(String mapKey) {
        if (CharSequenceUtil.isBlank(mapKey)) {
            throw new BaseException("缓存key不能为空");
        }
        RMap<Object, Object> map = redissonClient.getMap(mapKey, JsonJacksonCodec.INSTANCE);
        map.clear();
    }

    /**
     * 删除
     *
     * @param mapKey mapKey
     * @param hKey   hash key
     */
    public void hRemove(String mapKey, Object hKey) {
        this.hRemove(mapKey, hKey, () -> {
        });
    }

    /**
     * 加锁
     *
     * @param lockKey   缓存key
     * @param waitTime  等待时间
     * @param leaseTime 释放时间
     * @param fun       方法
     */
    public <T> T tryLock(String lockKey, long waitTime, long leaseTime, Supplier<T> fun) {
        if (CharSequenceUtil.isBlank(lockKey)) {
            throw new BaseException("分布式锁key不能为空");
        }
        boolean tryLock = false;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            tryLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (tryLock) {
                return fun.get();
            } else {
                throw new InterruptedException("获取锁失败");
            }
        } catch (InterruptedException e) {
            throw new BaseException(e.getMessage());
        } finally {
            if (tryLock) {
                lock.unlock();
            }
        }
    }

    /**
     * 加锁
     *
     * @param lockKey   缓存key
     * @param waitTime  等待时间
     * @param leaseTime 释放时间
     * @param fun       方法
     */
    public void tryLock(String lockKey, long waitTime, long leaseTime, Runnable fun) {
        if (CharSequenceUtil.isBlank(lockKey)) {
            throw new BaseException("分布式锁key不能为空");
        }
        boolean tryLock = false;
        RLock lock = redissonClient.getLock(lockKey);
        try {
            tryLock = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
            if (tryLock) {
                fun.run();
            } else {
                throw new BaseException("获取锁失败");
            }
        } catch (InterruptedException e) {
            throw new BaseException(e.getMessage());
        } finally {
            if (tryLock) {
                lock.unlock();
            }
        }
    }
}
